module Explode.Enemy;
private import Explode.Stage;
private import Explode.Characters;
private import Graphics.IGraphics;
private import Input.GamePad;
private import System.Console;
private import std.math;


/**
*	ロボットが「行きたい場所」をオブジェクト化したもの
**/
private abstract class CheckPoint{
	public int x = 0 , y = 0;
	public int xMove = 0 , yMove = 0;
	public int xDistance = 0x7fff , yDistance = 0x7fff;
	public bool found = false;
	public int bx = 0 , by = 0 , pbx = 0 , pby = 0;

	public void reset( int bxx , int byy , int pbxx , int pbyy ) {
		x = 0x7fff; y = 0x7fff;
		xMove = 0 ; yMove = 0;
		xDistance = 0x7fff; yDistance = 0x7fff;
		found = false;
		bx = bxx;
		by = byy;
		pbx = pbxx;
		pby = pbyy;
	} //

	public bool overwrite( int xx , int yy , int xd , int yd ) {
			x = xx ; y = yy;
			xDistance = xd ; yDistance = yd;
			if ( xd == 0 ) {
				xMove = 0;
				yMove = ( by > yy ) ? -1 : 1;
			} else {
				xMove = ( bx > xx ) ? -1 : 1;
				yMove = 0;
			}
			found = true;
			return true;
	} //
	
	public abstract bool compare( int xx , int yy );
} //



/**
*ランナーの高さに行ける場合、
*・それが1か所だけならばそこへ向かって歩く。
*・それが2か所以上の場合、その、近いほうへ行く。
*・距離が同じな2か所が候補の時、左側を選ぶ。
*/
private class CheckPointA : CheckPoint { // ロボットが行きたい場所(FC)
	public bool compare( int xx , int yy ) {
		int xd = abs( xx - bx ), yd = abs( yy - by );
		if ( yy != pby ) return false;
		if ( xDistance >= xd ) { //今の場所よりも同じか近い
			if ( xDistance != xd )
					return overwrite( xx , yy , xd , yd );
			else { // 同じ距離
				if ( xx < bx ) // 今のポイントより左にある
					return overwrite( xx , yy , xd , yd );
			}
		}
		return false;
	} //
} //
private class CheckPointA_pc : CheckPoint{ // ロボットが行きたい場所(PC版)
	public bool compare( int xx , int yy ) {
		//真下以外では常に左優先？
		int xd = abs( xx - bx ), yd = abs( yy - by );
		if ( yy !=  pby )     return false;
		if ( xDistance == 0 ) return false;
		if ( xd == 0 ) return overwrite( xx , yy , xd , yd );
		if ( xx <  x ) return overwrite( xx , yy , xd , yd );
		return false;
	} //
} //

/**
*ランナーの高さに行けない場合、
*・ランナーより高い位置に行きたがる。
*・ランナーより低い位置にしか行けない場合、そのいちばん高いところへ行きたがる。
*・行きたい高さに行ける場所が複数ある場合、ロボの真下が優先。
*・真下以外の複数の場合、左側に行ける場所があればそっちへ。なければ右へ。
*/
private class CheckPointB : CheckPoint { // ロボットが行きたい場所に行けない場合に行く場所
	public bool compare( int xx , int yy ) {
		int xd = abs( xx - bx ), yd = abs( yy - by );
		if ( yy > pby ) { // プレイヤーよりも低い
			if (  y <  pby ) return false;
			if ( yy <= y ) { //今と同じか上
				if ( yy <  y )	return overwrite( xx , yy , xd , yd );// 今より上
				if ( xd == 0 )	return overwrite( xx , yy , xd , yd );
				if ( xx <  x && xDistance != 0 ) // 同じ高さ・今より左・真下が見つかってない
							return overwrite( xx , yy , xd , yd );
				return false;
			}
				return false;
		}
		else{ // プレイヤーよりも高い位置
			if (  y > pby ) return overwrite( xx , yy , xd , yd );
			if ( yy >= y  ) { //今と同じか下
				if ( yy >  y ) return overwrite( xx , yy , xd , yd );// 今より下
				if ( xd == 0 ) return overwrite( xx , yy , xd , yd );
				if ( xx <  x && xDistance != 0 ) // 同じ高さ・今より左・真下が見つかってない
							return overwrite( xx , yy , xd , yd );
				return false;
			}
		}
		return false;
	} //

} //





public class Enemy : BaseCharacter{
	protected const int DROPINTERVAL  = 28;
	protected const int SLEEPTIME	  = 40;
	protected const int RESURRECTTIME = 40;
	protected const int IMGINDEX_FALL   = 20;
	protected const int IMGINDEX_ROPE   = 16;
	protected const int IMGINDEX_LADDER = 12;
	protected const int IMGINDEX_WALK   = 8;
	protected const int IMGINDEX_STAND  = 4;
	protected const int IMGINDEX_EGG    = 28;


	protected bool _deadFlag , _fallFlag , _ropeFlag , _ladderFlag;
	protected ICharacter	_target;
	protected GameStage		_stage;

	protected static int _IDCNT = 0;
	protected int _id ;
	public static int count(){ return _IDCNT;}

	protected int _actionCnt = 0 , _sleepCnt = 0 , _moveCnt = 0 , _resurrectCnt = 0 , _kickedCnt = 0;
	protected int _startX , _startY;
	protected int _dropGoldCnt;
	protected int _buruburu = 0;
	protected ISprite _subSprite;
	
	
	
	protected IGraphics _graphics;
	protected ITexture  _maptexture;
	protected string _textureName	 = "";

	/**
	*	アルゴリズム関係。 mixinが使えればすっきりする？
	*/
	protected string _cpu = "NORMAL";
	protected CheckPoint _checkPointA , _checkPointB ;
	protected void delegate( inout int xsMove , inout int ysMove ) checkAround;
	protected bool delegate( int x1 , int y1 , int x2 , int y2 ) checkFloorLine;
	protected void delegate( int x1 , int y1 , int x2 , int y2 ) checkHorizontal;
	protected void delegate( int x1 , int y1 , int x2 , int y2 ) checkVertical;
	protected bool delegate( ) resurrect;

	public int id(){return _id;}
	public void id( int v ) { _id = v; } 


	public this( GameStage stg , IGraphics g ) {
		_id = _IDCNT++;
		_stage = stg;
		_attribute  = Blocks.ENEMY;
		_graphics = g;
		_checkPointA	= new CheckPointA();
		_checkPointB	= new CheckPointB();
		cpu = "NORMAL";
		reset();
	} //
	public void target( ICharacter obj ) {
		_target = obj;
	}

	public void reset() {
		if ( _sprite is null ) {
			_sprite 	= _graphics.getSprite();
			_subSprite	= _graphics.getSprite();
		}
		if ( _textureName != _stage.enemyTextureName ) {
			_textureName = _stage.enemyTextureName;
			_texture = _graphics.createTexture( "ENEMY" , _stage.enemyTextureName  , 0 , 0 );
			_texture.setFrameSize( 32 , 48 , 0 );
		}
		_deadFlag = false;
		_ropeFlag = false;
		_ladderFlag = false;
		_fallFlag = false;
		_x=0;	_y=0;	_z=115;
		_hFace		  = 1;
		_goldCnt	  = 0;
		_sleepCnt	  = 0;
		_actionCnt	  = 0;
		_resurrectCnt = 0;
		_moveCnt	  = 0;
		_sleepCnt 	  = -0xff;
		_buruburu	  = 0;
		_hFace = ( ( _id % 2 ) == 0 ) ? 1:-1;
		_animCnt = _id;
		_alive	 = true;
		_startX		  = -1;
		_startY		  = -1;
		_kickedCnt	  = 0;
		_cpu	 = "NORMAL";

		_texture.frameIndex = 4;
		_sprite.reset();
		_sprite.visible = true;
		_sprite.moveTo( _x , _y );
		_sprite.setSize( Blocks.WIDTH , Blocks.HEIGHT + Blocks.HALFHEIGHT );
		_sprite.texture = _texture;
		_sprite.z = _z;

		_subSprite.reset();
		_subSprite.visible = true;
		_subSprite.setSize( Blocks.WIDTH , Blocks.HEIGHT );
		_subSprite.z = _z - 1 ;
	} //

	public bool action() {
		_stage.resetEnemyMask( bx , by );
		if ( _id == 0 ) updateActionTable();
		 int sp = checkActionTable();

		if ( _resurrectCnt-- > 0 ) resurrectAction();
		else
		if( _kickedCnt-- > 0 ) kickedAction();
		else {
		 if ( sp > 0 ) {
			_actionCnt++;
			for ( int i = 0 ; i < sp ; i++ ) {
				if ( _sleepCnt-- > 0 ) sleepAction();
				else				  walkAction();
			}
		 }
		}
		_stage.setEnemyMask( bx , by );
		return true;
	} //

	protected bool walkAction() {
		alias Blocks		B;
		alias B.WIDTH		bWidth;
		alias B.HEIGHT		bHeight;
		alias B.HALFWIDTH	hw;
		alias B.HALFHEIGHT	hh;
		int bx  = this.bx , by = this.by;
		int cb  = _stage.getXYBlock( bx , by );
		int fb  = _stage.getXYPointBlock( _x + hw , _y + bHeight );
		int fbb = _stage.getXYPointBlock( _x + hw,  _y + bHeight - 1 );
		int ctx = _x + hw;
		int xs  = _x % bWidth , ys = _y % bHeight;

		_ropeFlag = false;
		if ( B.isLadder( cb ) || B.isLadder( fbb ) ) _fallFlag = false;
		else
		if ( B.isRope(cb) ) {

			if ( ys <= ( hh - WALKSPEED ) ) _fallFlag = false;
			else 
			if ( B.isLadder( fb ) )			_fallFlag = false;
			else 							_fallFlag = true;

		}

		if ( ys == 0 && !B.isRope( cb ) ) _fallFlag = false;

		if ( _fallFlag && ys != 0 ) {
		} else { 
			int xsMove , ysMove;
			checkAround( xsMove , ysMove );
				_xMove = xsMove * WALKSPEED;
				_yMove = ysMove * WALKSPEED;
		}

		// 落下判定
		_fallFlag = false;
		if( !B.isMatch( _stage.getXYPointBlock( ctx , _y + bHeight ), B.BLOCK|B.LADDER|B.ENEMY|B.MBLOCK ) ) {
			int cbb = _stage.getXYPointBlock( _x + hw , _y + hh );
			if ( !B.isMatch( cbb , B.LADDER | B.ROPE ) && _sleepCnt < 0 ) { // ←同じ穴に落ちないようにするため
					_yMove	  = WALKSPEED;
					_fallFlag = true;
			}
		}



	// 壁衝突判定
		int xv = 1 , yv = 1;
			if ( _xMove != 0 ) { //横衝突
				int xb = _stage.getXYPointBlock( _x +  ( ( _xMove > 0 ) ? bWidth : -1) ,  _y + hh );
				if ( Blocks.isWall( xb ) || Blocks.isEnemy( xb ) ) xv = 0;
			}
			if ( _yMove != 0 ) { //縦衝突
				int yb = _stage.getXYPointBlock( _x + hw , _y + ( ( _yMove > 0 ) ? bHeight : -1 ) );
				if ( yMove >= 0 && B.isHHole( yb ) ) yb = yb & ~(B.HHOLE); // 下移動なら落とし穴マスクを外す
				if ( Blocks.isMatch( yb , B.BLOCK|B.ENEMY|B.HHOLE|B.MBLOCK) ) {
					yv = 0;
					_fallFlag = false;
				}
				xv = 0;
			}
		_xMove *= xv;
		_yMove *= yv;


	// 顔の向き・移動フラグ
		if ( _xMove != 0 ) _hFace = ( _xMove > 0 ) ? 1 : -1;
		bool mv = ( _xMove != 0 || _yMove != 0 );
	
	//金塊取得
		if ( xs == 0 && ys == 0 ) {
			if ( _goldCnt < 1 && B.isGold( cb ) ) {
				_goldCnt++;
				_stage.getGold( bx , by , false );
					_dropGoldCnt = _stage.random() % DROPINTERVAL;
					if ( _dropGoldCnt < 1 ) _dropGoldCnt = 1;
				_moveCnt = 0;
			}
		}


	//位置補正&移動
		int xsv = 0 , ysv = 0;
		_x += _xMove; _y += _yMove;
		 if ( _yMove * yv != 0 && xs != 0 ) xsv = WALKSPEED * ( ( xs >= hw ) ? 1 : -1 );
		 if ( _xMove * xv != 0 && ys != 0 ) ysv = WALKSPEED * ( ( ys >= hh ) ? 1 : -1 );
		_x += xsv; _y += ysv;
		moveTo( _x , _y );

	//金塊解放1(歩き中)
		 int mbx = this.bx() , mby = this.by() ;
		if ( !_fallFlag ) {
		 if ( _goldCnt > 0 && ( bx != mbx || by != mby ) ) { // マス目の位置が変わった
			_moveCnt++;
			if ( _moveCnt > _dropGoldCnt ) {
				if ( ( cb & ~( B.BLANK | B.HLADDER | B.GHOST | B.PLMASK | B.ENEMY | B.PLAYER ) ) == 0
					&& B.isMatch( fb , B.BLOCK | B.LADDER | B.ENEMY | B.MBLOCK | B.DIGGER ) ) {
					_stage.setGold( bx , by );
					_goldCnt--;
					_dropGoldCnt = _dropGoldCnt % DROPINTERVAL;
				}
			}
		 }
		} else { //金塊解放2(穴)
		  if ( B.isDigger( _stage.getXYBlock( mbx , mby ) ) ) {
			if ( mby != by ) dropGold(); 
			if ( _y % bHeight == 0  ) { 
				sleep();
				return true;
			}
		  }
		}
	// グラフィック変更
			if ( B.isRope( cb ) && xv != 0 ) _ropeFlag = true;
			if ( _y % bHeight != 0 && B.isRope( cb ) )
							{ _ropeFlag = false ;_fallFlag = true; }
			if ( B.isLadder( cb ) || B.isLadder( fb ) )
							{ _ladderFlag = true;_fallFlag = false;}

			if ( _xMove * xv != 0 ) _ladderFlag = false;
			if ( !_ladderFlag && _yMove * yv > 0 ) { _fallFlag = true; }
		 if ( mv ) {
			_animCnt++;
			int imgIndex = IMGINDEX_STAND;
			if ( _fallFlag )
						imgIndex = IMGINDEX_FALL;//20 ;
			else	if ( _ropeFlag )
						imgIndex = IMGINDEX_ROPE;//16 ;
			else	if ( _ladderFlag )
						imgIndex = IMGINDEX_LADDER;//12 ;
			else
						imgIndex = IMGINDEX_WALK;//8;
			 _texture.frameIndex = imgIndex + ( _animCnt /3 % 4 );
		 }

			return true;
	} //


	public bool sleepAction() {
		int bx  = this.bx , by = this.by;
		if ( _sleepCnt <= 8 ) {
			_buruburu = 0;
			 int hb = _stage.getXYBlock( bx , by - 1 );
			 int cb = _stage.getXYBlock( bx , by );

			if ( Blocks.isDigger( cb ) && _sleepCnt > 0 ) {
			 if ( Blocks.isBlock( hb ) || Blocks.isEnemy( hb ) ) _sleepCnt++;
			 else{
					 _animCnt++;

					 _texture.frameIndex = IMGINDEX_FALL + ( _animCnt /3 % 4 );
					 _y -= WALKSPEED ;
			 }
			} else {
				walkAction();
				if ( _x % Blocks.WIDTH == Blocks.HALFWIDTH) _sleepCnt++;
			}
		} else
		if ( _sleepCnt <  0xD ) {
			_buruburu = ( _sleepCnt % 2 ==0 ) ? 2 : -2;
		}
		return true;
	} //

	public bool kickedAction() {
		if ( _sleepCnt > 4 ) _sleepCnt = SLEEPTIME;
		_buruburu += ( _buruburu >0 ) ? -WALKSPEED : WALKSPEED;
		if ( _kickedCnt == 0 ) _buruburu = 0;
		return true;
	} //

	public bool drawingEntry( IGraphics g ) {
		g.addDrawingEntry( this );
		return true;
	} //

	public bool draw( IGraphics g) {
		// 金塊を持っている時は金塊を描画
		// テクスチャはステージのものを流用
		if ( _goldCnt > 0 ) {
			_subSprite.visible = true;
			int frameCnt = _stage.frameCnt;
			ITexture tex = _stage.texture;
			int ix = tex.frameIndex;
			{
				tex.frameIndex = 7 * 4 + ( ( frameCnt / 32 ) % 4 );
				_subSprite.texture = tex;
				_subSprite.moveTo( _x , y - Blocks.HALFHEIGHT );
				_subSprite.draw( g );
			}
			tex.frameIndex = ix;
		}
		if ( _sprite.visible ) {
			_sprite.flipH = ( _hFace < 0 );
			_sprite.moveTo( _x + _buruburu , _y - Blocks.HALFHEIGHT );
			_sprite.draw( g );
		}
		return true;
	} //


	public int getGold() {
		_goldCnt += _stage.getGold( bx , by , false );
		return 1;
	} //

	protected void setGold() {
		if ( !_stage.setGold( bx , by ) ) _stage.eraseGold( bx , by );
	} //

	public void moveTo( int xx , int yy ) {
		_x = xx ; _y = yy;
	} //






	protected void sleep() {
		_sleepCnt = SLEEPTIME;
		_fallFlag = false;
		moveTo( bx * Blocks.WIDTH , by * Blocks.HEIGHT );
	} //

	protected void dropGold() {
		if ( _goldCnt < 1 ) return;
		int bx = this.bx , by = this.by;
		int ub = _stage.getXYBlock( bx , by - 1 );
		 if ( Blocks.isNoBlock( ub ) )
			_stage.setGold( bx , by - 1 );
		  else // 金塊を置けない場合は消失させる
			_stage.eraseGold( bx , by - 1 );

			_goldCnt--;
	} //


	public bool onCollided( ICharacter obj ) {
		bool ret = false;
		switch ( obj.attribute ) {
			case Blocks.DIGGER:
				resurrect();
				break;
			case Blocks.MBLOCK:
				if ( obj.y <= y ) resurrect();
				break;
			case Blocks.PLAYER:
				if ( _resurrectCnt <= 0 ) ret = true;
				break;
			default:
				break;
		}
		return ret;
	} //

	public bool onKicked( ICharacter obj , int pow ) {
		alias Blocks B;
		int hf  = obj.hFace , bx = this.bx , by = this.by;
		int lrb = _stage.getXYBlock( bx + hf , by );
		if ( _fallFlag ) return false;
		if ( B.isMatch( lrb , B.BLOCK|B.MBLOCK|B.HHOLE ) )return false;
		if ( B.isMatch( lrb , B.ENEMY ) != 0 ) {
			if( !_stage.kickBlock( obj , bx + hf , by , pow ) ) return false;
		}
		if ( _resurrectCnt > 0 ) return true;
		_stage.resetEnemyMask( bx , by );
		_x = ( bx + hf ) * B.WIDTH;
		_stage.setEnemyMask( this.bx() , this.by() );
		_kickedCnt = B.WIDTH / WALKSPEED;
		_buruburu = ( hf ) * -WALKSPEED * _kickedCnt;
		return true;
	} //

	public bool kill() {
		reset();
		_alive = false;
		return true;
	} //



	//
	//
	//
	public void cpu( string c){
		_cpu = c;
		_startX = _x;
		_startY = _y;
		switch ( c ) {
			case "GBA":
				_checkPointA	= new CheckPointA();
				checkAround		= &checkAround_gba;
				checkFloorLine	= &checkFloorLine_gba;
				checkHorizontal = &checkHorizontal_gba;
				checkVertical   = &checkVertical_gba;
//				checkVertical   = &checkVertical_normal;
				resurrect		= &resurrect_gba;
				break;
			case "FC":
				_checkPointA	= new CheckPointA();
				checkFloorLine	= &checkFloorLine_normal;
				checkAround		= &checkAround_normal;
				checkHorizontal = &checkHorizontal_normal;
				checkVertical   = &checkVertical_fc;
				resurrect		= &resurrect_normal;
				break;
			case "PC":
				_checkPointA	= new CheckPointA_pc();
				checkAround		= &checkAround_normal;
				checkFloorLine	= &checkFloorLine_normal;
				checkHorizontal = &checkHorizontal_normal;
				checkVertical   = &checkVertical_normal;
				resurrect		= &resurrect_normal;
				break;
			case "NORMAL":
			case "BBR":
			default:
				_checkPointA	= new CheckPointA();
				checkAround		= &checkAround_normal;
				checkFloorLine	= &checkFloorLine_normal;
				checkHorizontal = &checkHorizontal_normal;
				checkVertical   = &checkVertical_normal;
				resurrect		= &resurrect_normal;
				break;
		}
	} //

	/**
	*	 行動範囲検索( Normal / FC / PC )
	*/
	protected void checkAround_normal( inout int xsMove , inout int ysMove ) {
		int pbx    = _target.bx , pby = _target.by;
		//同じマス目にいたら動きを止める。
		if ( bx == pbx && by==pby ) {
			ysMove = xsMove = 0;
			return;
		}

		_checkPointA.reset( bx , by , pbx , pby );
		_checkPointB.reset( bx , by , pbx , pby );

		// プレイヤーと同じ高さにいて地続きなら、直進する
		if ( checkFloorLine( bx , by , pbx , pby ) ) {
			xsMove = ( _target.x > _x ) ? 1 : -1 ;
		}else
			checkHorizontal( bx , by , pbx , pby );	// 行ける範囲を探索

		if ( _checkPointA.found ) // 同じ高さに行けそう
			{ xsMove = _checkPointA.xMove; ysMove = _checkPointA.yMove;}
		else
		if ( _checkPointB.found ) // 同じ高さに行けない
			{ xsMove = _checkPointB.xMove; ysMove = _checkPointB.yMove;}
	} //
	/**
	*	 行動範囲検索( GBA )
	*/
	protected void checkAround_gba( inout int xsMove , inout int ysMove ) {
		// GBA版では、プレイヤーの足元の座標を見る
		int pbx    = _target.bx , pby = ( (cast(int)_target.y) + Blocks.HEIGHT - WALKSPEED) >> Blocks.SHIFTY;
		_checkPointA.reset( bx , by , pbx , pby );
		_checkPointB.reset( bx , by , pbx , pby );

		// プレイヤーと同じ高さにいて地続きなら、直進する
		if ( checkFloorLine( bx , by , pbx , pby ) ) {
			xsMove = ( _target.x > _x ) ? 1 : -1 ;
		}else
			checkHorizontal( bx , by , pbx , pby );	// 行ける範囲を探索

		if ( _checkPointA.found ) // 同じ高さに行けそう
			{ xsMove = _checkPointA.xMove; ysMove = _checkPointA.yMove;}
		else
		if ( _checkPointB.found ) // 同じ高さに行けない
			{ xsMove = _checkPointB.xMove; ysMove = _checkPointB.yMove;}
	} //


	/**
	*	相手と地続きかどうかをチェック ( Normal / FC / PC )
	*/
	protected bool checkFloorLine_normal( int x1 , int y1 , int x2 , int y2 ) {
		alias Blocks B;
		if ( y1 != y2 || x2 < 0 ) return false;
		int xx1 , xx2 , yf = y1 + 1;
		if ( x1 <= x2 ) {
			xx1 = x1 + 1; xx2 = x2; 
		} else {
			xx1 = x2 ; xx2 = x1 -1;
		}
		for ( int i = xx1 ; i <= xx2 ; i++ ) {
			int fb = _stage.getXYBlock( i , yf );
			if ( !B.isMatch( fb , B.BLOCK | B.LADDER | B.ROPE | B.GOLD | B.MBLOCK | B.DIGGER | B.BONUS | B.GOAL) ) {
				int cb = _stage.getXYBlock( i , y1 );
				if ( !B.isMatch( cb , B.LADDER|B.ROPE ) ) return false;
			}
		}
		return true;
	} //

	/**
	*	相手と地続きかどうかをチェック ( GBA )
	*/
	protected bool checkFloorLine_gba( int x1 , int y1 , int x2 , int y2 ) {
		alias Blocks B;
		if ( y1 != y2 || x2 < 0 ) return false;
		int xx1 , xx2 , yf = y1 + 1;
		if ( x1 <= x2 ) {
			xx1 = x1 + 1; xx2 = x2; 
		} else {
			xx1 = x2 ; xx2 = x1 -1;
		}
		for ( int i = xx1 ; i <= xx2 ; i++ ) {
			int fb = _stage.getXYBlock( i , yf );
			if ( !B.isMatch( fb , B.BLOCK | B.LADDER | B.ROPE | B.GOLD | B.MBLOCK | B.DIGGER | B.GHOST | B.BONUS | B.GOAL) ) {
				int cb = _stage.getXYBlock( i , y1 );
				if ( !B.isMatch( cb , B.LADDER|B.ROPE ) ) return false;
			}
		}
		return true;
	} //





	/**
	*	横視界のチェック ( Normal / FC / PC )
	*/
	protected void checkHorizontal_normal( int x1 , int y1 , int x2 , int y2 ) {
		int getXYBlock( int x , int y ) {
			if ( y <= 0 || x <= 0 || x >= _stage.cols - 1 )return 0;
			return _stage.getXYBlock( x , y );
		} //
		void checkH( int x1 , int y1 , int x2 , int y2 , int lr) {
			alias Blocks B;
			int j = x1 , max = ( lr > 0 ) ? _stage.cols : 0;
			int cb , fb;
			int wall  = B.BLOCK | B.MBLOCK;
			int floor = B.BLOCK | B.DIGGER | B.MBLOCK;

			for ( ; ; j += lr ) { 
				if( j == max ) break;
				if( _checkPointA.found ) { 
					if ( abs( j - x1 ) > _checkPointA.xDistance ) return;
				}

				cb = getXYBlock( j , y1 );
				if ( B.isMatch( cb , wall ) ) break;
				fb = getXYBlock( j , y1 + 1 );

				if ( B.isLadder( cb | fb ) ) // 縦視界チェック
					checkVertical( j , y1 , x2 , y2 );
				else
				if ( !B.isMatch( fb , floor ) ) { // 足下がブロック以外
					checkVertical( j , y1 , x2 , y2 );
					if( !B.isMatch( cb , B.LADDER | B.ROPE  ) ) {
						 break;
					}
				}
				if ( lr == 0 ) break;
			}
			return;

		} //
		checkH( x1 , y1 , x2 , y2 , 0 ); //垂直
		if ( _checkPointA.found ) return;
		checkH( x1 - 1 , y1 , x2 , y2 , -1 );//左
		checkH( x1 + 1 , y1 , x2 , y2 ,  1 );//右
	} //

	/**
	*	横視界のチェック ( GBA )
	*/
	protected void checkHorizontal_gba( int x1 , int y1 , int x2 , int y2 ) {
		int getXYBlock( int x , int y ) {
			if ( y <= 0 || x <= 0 || x >= _stage.cols - 1 )return 0;
			return _stage.getXYBlock( x , y );
		} //
		void checkH( int x1 , int y1 , int x2 , int y2 , int lr) {
			alias Blocks B;
			int j = x1 , max = ( lr > 0 ) ? _stage.cols : 0;
			int cb , fb;
			int wall  = B.BLOCK | B.MBLOCK | B.HHOLE; // ←ここが違う
			int floor = B.BLOCK | B.DIGGER | B.MBLOCK | B.GHOST; // ←ここも違う

			for ( ; ; j += lr ) { 
				if( j == max ) break;
				if( _checkPointA.found ) { 
					if ( abs( j - x1 ) > _checkPointA.xDistance ) return;
				}

				cb = getXYBlock( j , y1 );
				if ( B.isMatch( cb , wall ) ) break;
				fb = getXYBlock( j , y1 + 1 );

				if ( B.isLadder( cb | fb ) ) // 縦視界チェック
					checkVertical( j , y1 , x2 , y2 );
				else
				if ( !B.isMatch( fb , floor ) ) { // 足下がブロック以外
					checkVertical( j , y1 , x2 , y2 );
					if( !B.isMatch( cb , B.LADDER | B.ROPE  ) ) {
						 break;
					}
				}
				if ( lr == 0 ) break;
			}
			return;

		} //
		checkH( x1 , y1 , x2 , y2 , 0 ); //垂直
		if ( _checkPointA.found ) return;
		checkH( x1 - 1 , y1 , x2 , y2 , -1 );//左
		checkH( x1 + 1 , y1 , x2 , y2 ,  1 );//右
	} //


	/**
	*	縦視界のチェック(Normal)
	*/
	protected void checkVertical_normal( int x1 , int y1 , int x2 , int y2 ) {
		alias Blocks B;
		int getXYBlock( int x , int y) {
			if ( y <= 0 || x <= 0 || x >= _stage.cols - 1 )return 0;
			return _stage.getXYBlock( x , y );
		}

		int i , cb = getXYBlock( x1 , y1 );
		int l = x1 - 1 , r = x1 + 1;
		/**
		*・はしごの頂上
		*・横にバーがあり、ランナーより高い位置にある地点
		*・斜め下にはしごかレンガかコンクリか掘ったレンガがあり、<s>ランナーより高い位置にある地点</s>
		*/
		if ( y1 <= 1 ) {
			 /*最上段にいるときは上視界サーチしない*/
		} else
		if ( B.isLadder(cb) ) {
			int e = _checkPointA.found ? y2 : 0;
			for ( i = y1 - 1 ; i >= e ; i-- ) { //上視界
				cb = getXYBlock( x1 , i );
					// 現在のブロックがはしご以外(≒はしごの最上段)なら「行ける場所」
				if ( !B.isLadder( cb ) || i == 0 ) {
					( ( i == y2 ) ? _checkPointA : _checkPointB).compare( x1 , i );
					break;
				}
				if ( _checkPointA.found ) {
					if (i != y2) continue;
				}
				int lb = getXYBlock( l , i );
				int rb = getXYBlock( r , i );
				// 横にバーがあるなら「行ける場所」
				if( B.isRope(lb|rb) ) {
					 ( ( i == y2 ) ? _checkPointA : _checkPointB ).compare( x1 , i );
					continue;
				}
				// 斜め下に床(ブロック・はしご)があるなら「行ける場所」
				int flb = getXYBlock( l , i + 1 );
				int frb = getXYBlock( r , i + 1 );
				if( Blocks.isMatch( flb|frb , B.BLOCK | B.LADDER | B.DIGGER | B.MBLOCK ) )
					 ( ( i == y2 ) ? _checkPointA : _checkPointB).compare( x1 , i );
			}
		} //

		int t = getXYBlock( x1 , y1 + 1 );
		if ( B.isMatch( getXYBlock( x1 , y1 + 1 ) , B.BLOCK | B.DIGGER | B.MBLOCK ) ) return;

		int rw = _checkPointA.found ? ( y2 + 1 ) : ( _stage.rows + 2 );

		for ( i = y1 ; i < rw ; i ++ ) { //下視界
			// 足下が床(ブロック・掘ったブロック)なら「行ける場所」
			int fb = getXYBlock( x1 , i + 1 );
			if ( B.isMatch( fb , B.BLOCK | B.DIGGER | B.MBLOCK ) ) {
				 ( ( i == y2 ) ? _checkPointA : _checkPointB ).compare( x1 , i );
				break;
			} else
			if ( i >= y2 ) {// プレイヤーと同じか下
				cb = getXYBlock( x1 , i );
				 if ( B.isMatch( cb , B.LADDER | B.ROPE  | B.DIGGER ) ) {
					// 梯子・バー・穴
					//ブロック・はしご・掘ったブロックの斜め上
					int flb2 = getXYBlock( l , i + 1 );
					int frb2 = getXYBlock( r , i + 1 );
					 if ( B.isMatch( flb2|frb2 , B.BLOCK | B.LADDER | B.MBLOCK | B.DIGGER ) ) {
						( ( i == y2 ) ? _checkPointA : _checkPointB).compare( x1 , i );
						continue;
					 }
					// バーの左右
					int lb2 = getXYBlock( l , i );
					int rb2 = getXYBlock( r , i );
					if ( B.isRope( lb2 |rb2 ) )
						( ( i == y2 ) ? _checkPointA : _checkPointB).compare( x1 , i );
				 }
			}
		} // end for
	} // end checkVertical_normal

	/**
	*	縦視界のチェック(GBA)
	*/
	protected void checkVertical_gba( int x1 , int y1 , int x2 , int y2 ) {
		int getXYBlock( int x , int y) {
			if ( y <= 0 || x <= 0 || x >= _stage.cols - 1 )return 0;
			return _stage.getXYBlock( x , y );
		}
		alias Blocks B;
		int i , cb = getXYBlock( x1 , y1 );
		int l = x1 - 1 , r = x1 + 1;
		/*
		・はしごの頂上
		・横にバーがあり、ランナーより高い位置にある地点
		・斜め下にはしごかレンガかコンクリか掘ったレンガがあり、ランナーより高い位置にある地点
		*/
		if ( y1 <= 1) {
			 /*最上段にいるときは上視界サーチしない*/
		} else
		if ( (cb & B.LADDER) != 0 ) {
			int e = ( _checkPointA.found ) ? y2 : 0;
			 for ( i = y1 - 1 ; i >= e ; i-- ) { //上視界
				cb = getXYBlock( x1 , i );
					// 現在のブロックがはしご以外(≒はしごの最上段)なら「行ける場所」
					if( ( cb & B.LADDER ) == 0 || i==0 ){
						 ( ( i == y2 ) ? _checkPointA : _checkPointB ).compare( x1 , i );
						break;
					}
				if( _checkPointA.found ){ 
					if ( i != y2 ) continue; 
				}
				if ( i < y2 ) {
					int lrb = getXYBlock( l , i ) | getXYBlock( r , i );
					// 横にバーがあるなら「行ける場所」
					if ( ( lrb & B.ROPE ) != 0 ) {
						 ( ( i == y2 ) ? _checkPointA : _checkPointB ).compare( x1 , i );
						continue;
					}
					// 斜め下に床(ブロック・はしご)があるなら「行ける場所」
					int fb = getXYBlock( l , i + 1 ) | getXYBlock( r , i + 1 );
					if ( ( fb & ( B.BLOCK | B.LADDER | B.MBLOCK | B.DIGGER )) != 0 )
						 ( ( i == y2 ) ? _checkPointA : _checkPointB ).compare( x1 , i );
				}
			 }
		} //

		int t = getXYBlock( x1 , y1 + 1 );
		if ( B.isMatch( getXYBlock( x1 , y1 + 1 ) , B.BLOCK | B.MBLOCK | B.DIGGER ) ) return;

		int rw = _checkPointA.found ? ( y2 + 1 ) : ( _stage.rows + 2 );

		for ( i = y1 ; i < rw ; i ++ ) { //下視界
			// 足下が床(ブロック・掘ったブロック)なら「行ける場所」
			int fb = getXYBlock( x1 , i + 1 );
			if ( B.isMatch( fb , B.BLOCK | B.MBLOCK | B.DIGGER ) ) {
				 ( ( i == y2 ) ? _checkPointA : _checkPointB ).compare( x1 , i );
				break;
			} else
			if ( i >= y2 ) {// プレイヤーと同じか下
				cb = getXYBlock( x1 , i );
				 if ( B.isMatch( cb , B.LADDER| B.ROPE | B.DIGGER ) ) {
					// 梯子・バー・穴
					//ブロック・はしご・掘ったブロックの斜め上
					int flb2 = getXYBlock( l , i + 1 );
					int frb2 = getXYBlock( r , i + 1 );
					 if ( B.isMatch( flb2|frb2 , B.BLOCK | B.LADDER | B.MBLOCK | B.DIGGER ) ) {
						( ( i == y2 ) ? _checkPointA : _checkPointB).compare( x1 , i );
						continue;
					 }
					// バーの左右
					int lb2 = getXYBlock( l , i );
					int rb2 = getXYBlock( r , i );
					if ( B.isRope( lb2 |rb2 ) )
						( ( i == y2 ) ? _checkPointA : _checkPointB).compare( x1 , i );
				 }
			}
		} // end for

	} // end checkVertical_gba

	/**
	*	縦視界のチェック(FC/DS)
	*/
	protected void checkVertical_fc( int x1 , int y1 , int x2 , int y2 ) {
		int getXYBlock( int x , int y) {
			if ( y <= 0 || x <= 0 || x >= _stage.cols - 1 )return 0;
			return _stage.getXYBlock( x , y );
		}
		alias Blocks B;
		int i , cb = getXYBlock( x1 , y1 );
		int l = x1 - 1 , r = x1 + 1;

		if ( y1 <= 1) {
			 /*最上段にいるときは上視界サーチしない*/
		} else
		if ( ( cb & B.LADDER ) != 0 ) {
			int fb = getXYBlock( l , y1 ) | getXYBlock( r , y1 );
			int  e = _checkPointA.found ? y2 : 0;
			for ( i = y1 - 1 ; i >= e ; i-- ) { //上視界
				cb = getXYBlock( x1 , i );
					// 現在のブロックがはしご以外(≒はしごの最上段)なら「行ける場所」
					if ( (cb & B.LADDER ) == 0 || i==0 ) {
						 ( ( i == y2 ) ?  _checkPointA : _checkPointB ).compare( x1 , i );
						break;
					}
				if ( _checkPointA.found ) {
					if ( i != y2 ) continue;
					fb =  getXYBlock( l , i + 1 ) | getXYBlock( r , i + 1 );
				}
				int lrb = getXYBlock( l , i ) | getXYBlock( r , i );
					// 横にバーがあるなら「行ける場所」
					if ( ( lrb & B.ROPE ) != 0 ) {
						 ( ( i == y2 ) ?  _checkPointA : _checkPointB ).compare( x1 , i );
						fb=lrb;
						continue;
					}
					// 斜め下に床(ブロック・はしご)があるなら「行ける場所」
					if( ( fb & ( B.BLOCK | B.LADDER | B.MBLOCK | B.DIGGER) ) != 0 )
						 ( ( i == y2 ) ?  _checkPointA : _checkPointB ).compare( x1 , i );
				fb = lrb;
			}
		} //end if( T.stage.isLadder(cb) )
		/*
		条件１）ランナーの高さ以下にある
		条件２）以下のどれかの位置にあるハシゴ、バー、落とし穴、金塊、掘ったレンガ
		・ブロックの左上・右上
		・はしごの左上・右上
		・バーの左上・右(右上ではない)
		*/

		if( ( getXYBlock( x1 , y1 + 1 ) & ( B.BLOCK | B.DIGGER | B.MBLOCK ) ) != 0 ) return;

		int rp = ( x1 == 1 ) ? B.ROPE : 0;
		int rw = _checkPointA.found ? ( y2 + 1 ) : ( _stage.rows + 2 );

		for ( i = y1 ; i < rw ; i ++ ) { //下視界
			// 足下が床(ブロック・掘ったブロック)なら「行ける場所」
			int fb = getXYBlock( x1 , i + 1 );
			if ( ( fb & (B.BLOCK | B.DIGGER| B.BOMBER) ) != 0 ) {
				 ( ( i == y2 ) ? _checkPointA : _checkPointB ).compare( x1 , i );
				break;
			} else
			if ( i >= y2 ) {// プレイヤーと同じか下
				cb = getXYBlock( x1 , i );

				if ( ( cb & (B.LADDER| B.ROPE | B.HHOLE | B.GOLD | B.DIGGER) ) != 0 ){

					//ブロック・はしご・バー・掘ったブロックの左上
					int frb = getXYBlock( r , i + 1 ) & ~rp;
					 if ( (frb & (B.BLOCK | B.LADDER | B.ROPE | B.MBLOCK | B.DIGGER | B.BOMBER )) != 0 ) {
						( ( i == y2 ) ? _checkPointA : _checkPointB ).compare( x1 , i );
						continue;
					 }
					//ブロック・はしご・掘ったブロックの右上
					int flb = getXYBlock( l , i + 1 );
					 if ( (flb & (B.BLOCK | B.LADDER | B.MBLOCK | B.DIGGER | B.BOMBER)) != 0 ) {
						( ( i == y2 ) ? _checkPointA : _checkPointB ).compare( x1 , i );
						continue;
					 }
					// バーの右(右上ではない)・左から2列目にあるバーの左
					int lb = getXYBlock( l , i );
					int rb = ( rp == 0 ) ? 0 : getXYBlock( r , i );
					if( ( (lb|rb) & B.ROPE ) != 0 ) {
						( ( i == y2 ) ? _checkPointA : _checkPointB ).compare( x1 , i );
						continue;
					}
				}
			}
		} // end for
	} // end checkVertical


	/**
	*	復活位置探索 ( Normal / FC / PC )
	**/
	protected bool resurrect_normal() {
		alias Blocks B;
		bool isBlank( int b) {
			return !B.isMatch( b, (B.BLOCK|B.LADDER|B.ROPE|B.HHOLE|B.GOLD|B.DIGGER|B.MBLOCK|B.GOAL) );
		} //
		int bx = this.bx , by = this.by;
		_stage.resetEnemyMask( bx , by );

		_sleepCnt = 0;
		_resurrectCnt = RESURRECTTIME;
		_buruburu = 0;
		_stage.killCnt = _stage.killCnt + 1;

		Enemy.lastActPtr = _id;

		// 金塊を持ったまま死亡したら消失させる
		if ( _goldCnt > 0 ) {
			_stage.eraseGold( bx , by );
			_goldCnt--;
		}

		int cols = _stage.cols , rows = _stage.rows;
		int r = ( _stage.random() % ( cols - 2 ) ) + 1;
		int rb = _stage.getXYBlock( r , 2 );
		bool f = false;
		if ( isBlank( rb ) )
			moveTo( r * B.WIDTH , 2 * B.HEIGHT );
		else {
			for ( int xx = r + 1 ; xx < cols - 1 ; xx++ ) {
				int lx = _stage.getXYBlock( xx , 2 );
				if ( isBlank( lx ) ) {
					moveTo( xx * B.WIDTH , 2 * B.HEIGHT );
					f = true;
					break;
				}
			}
			if ( !f ) {
				for ( int yy = 3 ; yy < rows - 1 ; yy++ ) {
					for( int xx2 = 1 ; xx2 < cols - 1 ; xx2++ ) {
						int lx2 = _stage.getXYBlock( xx2 , yy );
						if ( isBlank( lx2 ) ) {
							moveTo( xx2 * B.WIDTH , yy * B.HEIGHT );
							f = true;
							break;
						}
					}
					if ( f ) break;
				}
				if ( !f ) moveTo( 3 * Blocks.WIDTH , ( rows - 1 ) * Blocks.HEIGHT );
			}
		}
		_stage.setEnemyMask( this.bx() , this.by() );
		return true;
	} //


	/**
	*	復活位置探索 ( GBA )
	**/
	protected bool resurrect_gba() {
		alias Blocks B;
		bool isBlank( int b) {
			return !B.isMatch( b, (B.BLOCK|B.LADDER|B.ROPE|B.HHOLE|B.GOLD|B.DIGGER|B.MBLOCK|B.GOAL|B.PLAYER) );
		} //
		bool mv( int xx , int yy ) {
			moveTo( xx , yy );
			_stage.setEnemyMask( this.bx() , this.by() );
			return true;
		} //
		int bx = this.bx , by = this.by;
		int rows = _stage.rows , cols = _stage.cols;
		_stage.resetEnemyMask( bx , by );

		_sleepCnt = 0;
		_resurrectCnt = RESURRECTTIME;
		_buruburu = 0;
		_stage.killCnt = _stage.killCnt + 1;

		Enemy.lastActPtr = _id;

		// 金塊を持ったまま死亡したら消失させる
		if ( _goldCnt > 0 ) {
			_stage.eraseGold( bx , by );
			_goldCnt--;
		}


		for ( int i = 0 ; i < 4 ; i++ ) {
			int ix = bx + ( i % 2 ) , iy = 1 + ( i >> 1 );
			int k  = _stage.getXYBlock( ix , iy );
			if ( isBlank( k ) )
				return mv( ix * B.WIDTH , iy * B.HEIGHT );
		}
		int s = _stage.getXYPointBlock( _startX , _startY );
		if (  isBlank( s ) )
				return mv( _startX , _startY );


		for ( int y = 1 ; y < rows -1 ; y++ ) {
			for ( int x = 1 ; x < cols - 1 ; x++ ) {
				int lx = _stage.getXYBlock( x , y );
				if ( isBlank( lx ) ) {
					return mv( x * B.WIDTH , y * B.HEIGHT );
				}
			}
		}
		return mv( _startX , _startY );
	} //


	protected bool resurrectAction() {
		alias Blocks B;
		int bx = this.bx , by = this.by;
		int cb = _stage.getXYBlock( bx , by );

		_sprite.visible = true;

		if ( _resurrectCnt > 30 )	_texture.frameIndex = IMGINDEX_EGG ;
		if ( _resurrectCnt > 20 )	_texture.frameIndex = IMGINDEX_EGG + 1;
		if ( _resurrectCnt > 20 )	_texture.frameIndex = IMGINDEX_EGG + 2;
		else						_texture.frameIndex = IMGINDEX_EGG + 3;

		return true;
	} //




	/**
	*	ロボットの行動順を決めるアクションテーブル。(ファミコン準拠)
	*	ロボットが増えれば増えるほど1/3に近づいていく。
	**/
	protected static int[] baseTable; //int[]
	protected static int[] actionTable;  //int[]
	protected static int   basePtr;
	protected static int   lastActPtr;
	public static void initActionTable( int cnt ) {
		int  a , b , c , n = cnt + 1 , i;
		if ( n > 8 + cnt ) { n = 8 + cnt; }
		a = n / 3;
		b = a;
		c = a;

		if ( n % 3 >= 2 ) {
			b++;
			c++;
		} else
		if ( n % 3 >= 1 ) c++;

		int[] bt = [a,b,c], at;
		for ( i = 0 ; i < cnt ; i++ ) at ~= 0;
		baseTable   = bt;
		actionTable = at;
		basePtr		= 0;
		lastActPtr	=-1;
	} //


	protected static void  updateActionTable() {
		int[] at = actionTable, bt = baseTable;
		int mv = bt[basePtr % bt.length];
		int lp = 0;
		lastActPtr++;
		for ( int i = 0 ; i < mv ; i++ ) {
			lp = ( lastActPtr + i ) % at.length;
			at[ lp ]++;
		}
		if ( at.length == 1 ) at[ 0 ] = mv;

		actionTable = at;
		lastActPtr = lp;
		basePtr++;
	} //

	protected int checkActionTable(){
		try{
			int ret = actionTable[ _id ];
			actionTable[ _id ] = 0;
			return ret;
		}catch(Exception e){
			Console.writeln("exception: %s , %d %d" , e.toString() , _id , actionTable.length );
			return 1;
		}
	} //


	invariant {
		assert( _x >= 0 );
		assert( _y >= 0 );
	}
} //


//+/
